package codename.carrot.bomberman.network;

import android.util.Log;
import codename.carrot.bomberman.network.MessageHandler.MessageReceiver;
import codename.carrot.bomberman.network.client.ClientConnection;
import codename.carrot.bomberman.network.interfaces.NetworkMessage;
import codename.carrot.bomberman.network.listener.NetworkReceiveListener;
import codename.carrot.bomberman.network.listener.NetworkServerReceiveListener;
import codename.carrot.bomberman.network.listener.NetworkStateListener;
import codename.carrot.bomberman.network.server.ServerConnection;

/**
 * An instance of this class is the main handler of the network. The instance is
 * responsible for setting up and tearing down a connection. It is also the link
 * between the network and the game.
 * 
 * Only one instance of this class exist at all times (singleton).
 * 
 * @author Group A4.
 * 
 */
public class NetworkConnection {

	/**
	 * String for debugging.
	 */
	private String DEBUG = this.getClass().getSimpleName();
	/**
	 * The instance of this class (singleton).
	 */
	private static NetworkConnection instance;
	/**
	 * A client connection, handling setting up and dealing with the network as
	 * a client.
	 */
	private ClientConnection clientConnection;
	/**
	 * A server connection, handling setting up and dealing with the network as
	 * a server.
	 */
	private ServerConnection serverConnection;
	/**
	 * The message handler for the network.
	 */
	private MessageHandler messageHandler;
	/**
	 * The maximum number of connections that can be established. MAX
	 * CONNECTIONS = THE SERVER + THE CLIENT RUNNING ON THE SERVER + (3*CLIENT) * = 5
	 */
	public static final int MAX_CONNECTIONS = 5;
	/**
	 * The port used for network connections.
	 */
	public static final int PORT = 2013;

	public enum NetworkState {
		/**
		 * The network is not connected, listening for a connection or accepting
		 * incoming connections. (DEAD).
		 */
		NONE,
		/**
		 * An error has occured in the network.
		 */
		ERROR,
		/**
		 * The network is listening and ready for incoming connections.
		 */
		LISTENING,
		/**
		 * The network is connected to one or more clients, while also listening
		 * for more.
		 */
		LISTENING_CONNECTED,
		/**
		 * The network is connecting to a client or server.
		 */
		CONNECTING,
		/**
		 * The network is connected.
		 */
		CONNECTED
	};

	/**
	 * Set up the network connection and creating the message handler.
	 */
	private NetworkConnection() {
		try {
			this.messageHandler = new MessageHandler();
		} catch (Exception e) {
			Log.e(DEBUG, "Failed to create message handler", e);
		}
	}

	/**
	 * Return the instance of the Network Connection.
	 * 
	 * @return The instance of the network connection.
	 */
	public static NetworkConnection getInstance() {
		if (NetworkConnection.instance == null)
			NetworkConnection.instance = new NetworkConnection();

		return NetworkConnection.instance;
	}

	/**
	 * Get the server connection running.
	 * 
	 * @return The server connection or null if the server connection does not
	 *         exist.
	 */
	public ServerConnection getServerConnection() {
		return serverConnection;
	}

	/**
	 * Terminate the network.
	 */
	public void terminate() {
		if (clientConnection != null)
			clientConnection.terminate();

		if (serverConnection != null)
			serverConnection.terminate();

		messageHandler = null;
		NetworkConnection.instance = null;
	}

	/**
	 * Cancel accepting new clients - setting the network to connected.
	 */
	public void cancelAccept() {
		if (clientConnection != null)
			clientConnection.cancelAccept();

		if (serverConnection != null)
			serverConnection.cancelAccept();
	}

	/**
	 * Add a listener for network state events. - Forwarded to the message
	 * handler.
	 * 
	 * @param nsl
	 *            The listener to be added.
	 */
	public void addStateListener(NetworkStateListener nsl) {
		if (nsl == null || messageHandler == null)
			return;
		messageHandler.addStateListener(nsl);
	}

	/**
	 * Remove a listener for network state events. - Forwarded to the message
	 * handler.
	 * 
	 * @param nsl
	 *            The listener to be removed.
	 */
	public void removeStateListener(NetworkStateListener nsl) {
		if (nsl == null || messageHandler == null)
			return;
		messageHandler.removeStateListener(nsl);
	}

	/**
	 * Remove a listener for network received events targeted the server side. -
	 * Forwarded to the message handler.
	 * 
	 * @param nsrl
	 *            The listener to be removed.
	 */
	public void addServerReceiveListener(NetworkServerReceiveListener nsrl) {
		if (nsrl == null || messageHandler == null)
			return;
		messageHandler.addServerReceiveListener(nsrl);
	}

	/**
	 * Remove a listener for network received events targeted the server side. -
	 * Forwarded to the message handler.
	 * 
	 * @param nsrl
	 *            The listener to be removed.
	 */
	public void removeServerReceiveListener(NetworkServerReceiveListener nsrl) {
		if (nsrl == null || messageHandler == null)
			return;
		messageHandler.removeServerReceiveListener(nsrl);
	}

	/**
	 * Add a listener for receive events targeted the client side. - Forwarded
	 * to the message handler.
	 * 
	 * @param nrl
	 *            The listener to be added.
	 */
	public void addReceiveListener(NetworkReceiveListener nrl) {
		if (nrl == null || messageHandler == null)
			return;
		messageHandler.addReceiveListener(nrl);
	}

	/**
	 * Remove a listener for network received events targeted the client side. -
	 * Forwarded to the message handler.
	 * 
	 * @param nrl
	 *            The listener to be removed.
	 */
	public void removeReceiveListener(NetworkReceiveListener nrl) {
		if (nrl == null || messageHandler == null)
			return;
		messageHandler.removeReceiveListener(nrl);
	}

	/**
	 * Set up a connection as a server.
	 */
	public void connectAsServer() {
		this.serverConnection = new ServerConnection(this.messageHandler);
	}

	/**
	 * Set up a connection as a client.
	 * 
	 * @param ipAddress
	 *            The address to connect to.
	 */
	public void connectAsClient(String ipAddress) {
		this.clientConnection = new ClientConnection(ipAddress,
				this.messageHandler);
	}

	/**
	 * Send a message over the network to all clients.
	 * 
	 * This is the same as calling sendMessage(message, -1, clientFrom,
	 * MessageReceiver.INCLUSIVE);
	 * 
	 * @param message
	 *            The message to send.
	 * @param clientFrom
	 *            The id of the client that sent the message.
	 */
	public void sendMessage(NetworkMessage message, int clientFrom) {
		sendMessage(message, -1, clientFrom, MessageReceiver.INCLUSIVE);
	}

	/**
	 * Send a message over the network directly to, excluding or including a
	 * specific client.
	 * 
	 * This means that the MessageReceiver enum dictates if the message sent is
	 * to be: 1. Sent independently to the client. 2. Sent to everyone except
	 * the client (exclusive). 3. Sent to everyone including the client.
	 * 
	 * @param message
	 *            The message to send.
	 * @param clientTo
	 *            The client that is to either receive or not receive the
	 *            message.
	 * @param clientFrom
	 *            The id of the client that sent the message.
	 * @param messageReceiver
	 *            An enum of either independent send, exclusive send or
	 *            inclusive send.
	 */
	public void sendMessage(NetworkMessage message, int clientTo,
			int clientFrom, MessageReceiver messageReceiver) {
		if (this.clientConnection != null)
			clientConnection.send(message);
		else if (this.serverConnection != null)
			serverConnection.send(message, clientTo, clientFrom, messageReceiver);
	}

}
